﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RuleOfReady.Core.Utility;
using RuleOfReady.Core.DomainModels.Tiles;
using RuleOfReady.Core.DomainModels.Sets;
using Combinatorics.Collections;

namespace RuleOfReady.Core.Engines
{
    public class TileEngine
    {
        #region Private Fields

        /// <summary>
        /// Constant: number of identical tiles of each tile type in the game
        /// </summary>
        private const int TilesPerTypeInGame = 4;

        /// <summary>
        /// When red bonus tiles are used (Japanese: akidora), this is the suit number used for them
        /// </summary>
        private const MahjongSuitNumber redBonusNumber = MahjongSuitNumber.Five;

        /// <summary>
        /// When the red bonus tiles are used, these are the number of tiles per suit (there are normally 4 tiles)
        /// </summary>
        private readonly IReadOnlyDictionary<MahjongSuitType, int> numRedTilesPerSuit =
            new Dictionary<MahjongSuitType, int>()
            {
                {MahjongSuitType.Bamboo, 1},
                {MahjongSuitType.Character, 1},
                {MahjongSuitType.Dot, 2},
            };

        #endregion

        #region Public (read-only) Properties

        /// <summary>
        /// A list of Honor types, in order
        /// </summary>
        public IEnumerable<MahjongHonorType> HonorTileTypes { get; private set; }

        /// <summary>
        /// A list of Suit types, in order
        /// </summary>
        public IEnumerable<MahjongSuitType> SuitTileTypes { get; private set; }

        /// <summary>
        /// A list of suit numbers, in order
        /// </summary>
        public IEnumerable<MahjongSuitNumber> SuitTileNumbers { get; private set; }

        /// <summary>
        /// A list of all Terminals (suit tiles that are 1 or 9) 
        /// (Japanese: Rōtōhai)
        /// </summary>
        public IEnumerable<MahjongTile> TerminalTiles { get; private set; } 

        /// <summary>
        /// A list of all Major tiles, (Terminals + Honors) 
        /// (Japanese: Yaochūhai)
        /// </summary>
        public IEnumerable<MahjongTile> MajorTiles { get; private set; }

        /// <summary>
        /// A list of all Simple tiles (suit tiles from 2 thru 8)
        /// (also known as Minor or Middle Tiles)
        /// (Japanese: Chunchanpai or Tanyaohai)
        /// </summary>
        public IEnumerable<MahjongTile> SimpleTiles { get; private set; }

        /// <summary>
        /// A Dictionary, keyed by Suit Type of all possible sequences
        /// </summary>
        public IReadOnlyDictionary<MahjongSuitType, IEnumerable<MahjongSequence>>
            SequencesBySuit { get; private set; }

        /// <summary>
        /// A list of all sequences: 3 consecutive tiles of the same suit, 
        /// (e.g. 3-dot 4-dot 5-dot)
        /// </summary>
        public IEnumerable<MahjongSequence> Sequences
        {
            get { return SequencesBySuit.Values.SelectMany(seq => seq); }
        }

        /// <summary>
        /// A list of all partial sequences: 2 tiles out of a sequence
        /// </summary>
        public IEnumerable<MahjongPartialSequence> PartialSequences
        {
            get { return Sequences.SelectMany(seq => seq.PartialSequences); }
        }

        #endregion

        #region Constructor

        /// <summary>
        /// Create a new Tile Engine. (This is a somewhat heavy object)
        /// </summary>
        public TileEngine()
        {
            this.HonorTileTypes = Enum.GetValues(typeof(MahjongHonorType)).Cast<MahjongHonorType>();
            this.SuitTileTypes = Enum.GetValues(typeof(MahjongSuitType)).Cast<MahjongSuitType>();
            this.SuitTileNumbers = Enum.GetValues(typeof(MahjongSuitNumber)).Cast<MahjongSuitNumber>();

            this.TerminalTiles = this.GenerateTerminalTiles();
            this.SimpleTiles = this.GenerateSimpleTiles();

            this.MajorTiles = this.HonorTileTypes.Select(honorType => new MahjongHonorTile(honorType))
                                                 .Concat(this.TerminalTiles);

            this.SequencesBySuit = new Dictionary<MahjongSuitType, IEnumerable<MahjongSequence>>(3)
                { 
                    {MahjongSuitType.Bamboo,    this.GenerateSequencesForSuit(MahjongSuitType.Bamboo)},
                    {MahjongSuitType.Character, this.GenerateSequencesForSuit(MahjongSuitType.Character)},
                    {MahjongSuitType.Dot,       this.GenerateSequencesForSuit(MahjongSuitType.Dot)}
               };



        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Create a new tile set of 136 tiles, 4 of each type
        /// </summary>
        /// <returns></returns>
        public IList<MahjongTile> CreateGameTileSet()
        {
            return CreateGameTileSet(useRedBonusTiles:false);
        }

        /// <summary>
        /// Create a new tile set of 136 tiles, 4 of each type, optionally swapping in the red tiles
        /// </summary>
        /// <param name="useRedBonusTiles">Swap in red tiles?</param>
        /// <returns></returns>
        public IList<MahjongTile> CreateGameTileSet(bool useRedBonusTiles)
        {
            var tileSet = new List<MahjongTile>();
            foreach (MahjongSuitType suitType in this.SuitTileTypes)
            {
                foreach (MahjongSuitNumber suitNumber in this.SuitTileNumbers)
                    if (!useRedBonusTiles || !(suitNumber == TileEngine.redBonusNumber))
                        tileSet.AddRange(CreateTilesForSet(suitType, suitNumber));
                    else
                        tileSet.AddRange(CreateRedTilesForSet(suitType, suitNumber));
            }

            foreach (MahjongHonorType honorType in this.HonorTileTypes)
            {
                tileSet.AddRange(CreateTilesForSet(honorType));
            }

            return tileSet;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Create tiles for the given suit and number for the game
        /// </summary>
        /// <param name="suitType">suit to create</param>
        /// <param name="suitNumber">number to create</param>
        /// <returns></returns>
        private IEnumerable<MahjongTile> CreateTilesForSet(MahjongSuitType suitType, MahjongSuitNumber suitNumber)
        {
            return Enumerable.Repeat(new MahjongSuitTile(suitType, suitNumber), TileEngine.TilesPerTypeInGame);
        }

        /// <summary>
        /// Create tile for the given honor for the game
        /// </summary>
        /// <param name="honorType">honor to create</param>
        /// <returns></returns>
        private IEnumerable<MahjongTile> CreateTilesForSet(MahjongHonorType honorType)
        {
            return Enumerable.Repeat(new MahjongHonorTile(honorType), TileEngine.TilesPerTypeInGame);
        }

        /// <summary>
        /// Create tiles for the given suit and number for the game, with Red Bonus tiles swapped in as appropriate
        /// </summary>
        /// <param name="suitType">suit to create</param>
        /// <param name="suitNumber">number to create</param>
        /// <returns></returns>
        private IEnumerable<MahjongTile> CreateRedTilesForSet(MahjongSuitType suitType, MahjongSuitNumber suitNumber)
        {
            if (suitNumber != TileEngine.redBonusNumber)
                return this.CreateTilesForSet(suitType,suitNumber);
            
            int numRedTiles = this.numRedTilesPerSuit[suitType];
            int numNormalTiles = TileEngine.TilesPerTypeInGame - numRedTiles;

            return Enumerable.Repeat(new MahjongSuitTile(suitType, suitNumber, isRedBonus: true), numRedTiles)
                             .Concat(Enumerable.Repeat(new MahjongSuitTile(suitType, suitNumber), numNormalTiles));
        }

        /// <summary>
        /// Generates a list of all sequences for a suit
        /// </summary>
        /// <param name="suitType">suit to make the sequences</param>
        /// <returns></returns>
        private IEnumerable<MahjongSequence> GenerateSequencesForSuit(MahjongSuitType suitType)
        {
            IList<MahjongSuitTile> tiles = 
                    this.SuitTileNumbers
                        .Select(number => new MahjongSuitTile(suitType, number))
                        .ToList();

            for(int startingIdx = 0; tiles.Count - startingIdx >= 3; startingIdx += 1)
            {
                yield return new MahjongSequence(tiles.Skip(startingIdx).Take(3));
            }
        }

        /// <summary>
        /// Generate all of the Terminal tiles (1 and 9 of each suit)
        /// </summary>
        /// <returns>list of Terminals</returns>
        private IEnumerable<MahjongTile> GenerateTerminalTiles()
        {
            int lowTerminal = 1;
            int highTerminal = this.SuitTileNumbers.Count();
            foreach (MahjongSuitType suitType in this.SuitTileTypes)
            {
                yield return new MahjongSuitTile(suitType, lowTerminal);
                yield return new MahjongSuitTile(suitType, highTerminal);
            }
        }
        
        /// <summary>
        /// Generate all of the Simple Tiles (2 thru 8 of each suit)
        /// </summary>
        /// <returns>list of Simples</returns>
        private IEnumerable<MahjongTile> GenerateSimpleTiles()
        {
            int lowestSimple = 2;
            int highestSimple = this.SuitTileNumbers.Count() - 1;
            foreach (MahjongSuitType suitType in this.SuitTileTypes)
            {
                for (int suitNumber = lowestSimple; suitNumber <= highestSimple; suitNumber++)
                {
                    yield return new MahjongSuitTile(suitType, suitNumber);
                }
            }
        }

        #endregion
    }
}
